前面有說到UserAuthentication()
跟UserAuthorization()
,這兩個的差別在於:前者用於驗證登入者是誰,後者則決定登入者可以做什麼。
舉例來說,一個員工要登入員工系統,他必須輸入帳號(如員工 ID、姓名或是 email)、密碼,系統才能知道是誰登入了,這就是 Authentication(驗證),處理 Authentication 的方式有 Cookie、Token、第三方驗證(OAuth 或 API-token)、OpenId 及 SAML。
而當員工登入系統後,一般員工通常不會有跨部門或是管理權限,他只能看到他自己或所屬部門的資訊,例如生產部員工看不到會計部的財務,不過會計部為了計算成本卻看得到生產部的原料價格,這就是 Authorization (授權)。
在決定登入者可以做什麼前,必須先知道登入者是誰,所以UserAuthentication()
必須放在UserAuthorization()
前面。
ASP.NET Core Identity 使用的是基於 Claim 的驗證,要了解 Claim,必須先了解 Claim、ClaimsIdentity 跟 ClaimsPrincipal 是什麼。
Claim 就是關於使用者的一些資訊,Claim Type 跟 Claim Value(可以不用給)就組成一個 Claim,Claim 可以是姓名、電話、角色、Email甚至是角色等等。Authorization 就是用 Claim 判斷使用者有無授權。
ClaimsIdentity 則是多個 Claim 的集合,像是駕照上面記錄了姓名、生日、電話,駕照就是一個 ClaimsIdentity。
ClaimsPrincipal 是多個 ClaimsIdentity 的集合,台灣人都有身分證跟健保卡,有些人還有駕照,身分證、健保卡跟駕照都是 ClaimsIdentity,持有它們的人就是 ClaimsPrincipal。
而每一個 HTTP request 都會產生 HttpContext 物件,該物件就存有目前 request 的資訊,其下可以找到一個型別為 ClaimsPrincipal 的 Property 名為 User,這個 Property 就是由UseAuthentication()
引入的 Authentication Middleware 產生的。
登入機制可以用 Cookie 或是 JWT 實作,但 Authentication Middleware 怎麼知道要用哪個方法產生 User Property?那就要看 Authentication Scheme 跟 Authentication Handlers。
Authentication Handlers 就是處理驗證的方式,ASP.NET Core Identity 可以呼叫AuthenticateAsync()
API 去驗證使用者已登入,如驗證失敗就呼叫ChallengeAsync()
將使用者導回登入頁面,如授權失敗則用ForbidAsync()
禁止使用者訪問,當然也可以自己實作這些行為。下面例子中用了 JWT 跟 Cookie 的驗證方式,如果用了前者,就必須驗證 JWT token 並產生 ClaimsPrincipal 回傳到 HttpContext.User 中;使用後者則會檢查當前 request 的 cookie並產生 ClaimsPrincipal。
services.AddAuthentication()
.AddJwtBearer()
.AddCookie();
用了任何一種方式註冊 Authentication Handlers 就稱為 Authentication Scheme,每個 Authentication Scheme 都有一個獨特的名字以識別,且可以自己設定 Authentication Handlers,下面的程式結果跟上面會是一樣,因為它們都有預設的 Scheme Name。
services.AddAuthentication()
.AddJwtBearer("Bearer")
.AddCookie("Cookies");
Blazor 用的驗證方式跟 ASP.NET Core 一樣,不過 Blazor WebAssembly 跟 Blazor Server 又有不同,前者的驗證就像任何前端網站一樣可以被繞過,因為使用者端的程式可以被使用者改動,因此發送資料的 API 端一定也需要驗證;後者則可用內建的 AuthenticationStateProvider 取得前面說的 HttpContext.User,筆者此前就是自己繼承並覆寫這項 Service 實作 JWT 驗證的。
AuthenticationStateProvider 就是昨天說到的<AuthorizeView>
及<CascadingAuthenticationState>
可以取得當前驗證狀態的原因,但如果沒有要覆寫預設驗證機制的話,建議不要自己在 Component 注入一個 AuthenticationStateProvider 出來,直接使用的缺點很明顯,若當前 request 的驗證狀態有異動,因為你改動了這個 Component 的驗證機制,該 Component 就不會被告知。
可以看到下圖,ApiAuthenticationStateProvider
繼承了AuthenticationStateProvider
,並覆寫了Task<AuthenticationState>
,這個 Property 可以取得當前的驗證狀態,MarkUserAsAuthenticated()
跟MarkUserAsLoggedOut()
則是筆者自己寫的方法用以標示使用者通過驗證及登出系統,NotifyAuthenticationStateChanged()
顧名思義會通知各個 Component 當前驗證狀態,這就是自己繼承並覆寫的案例。
下圖則是在 Component 取得當前 request 的 HttpContext.User 及 Claims 的作法,不過這裡是先利用服務取得 User 再取得其下 Claims,其實是多此一舉了。
如果只是要取得 HttpContext.User,只要如下圖般就可以了,因為 Task<AuthenticationState>
會以[CascadingParameter]
的方式層層傳遞下去。
Ref:Introduction to Authentication in ASP.NET Core
Ref:ASP.NET Core Blazor authentication and authorization